package com.ctsi.filestore.app.service;

import com.ctsi.commons.util.id.UUIDGenerator;
import com.ctsi.filestore.app.params.MultipartFileParam;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;

import javax.activation.MimetypesFileTypeMap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

@Service
public class StorageServiceImpl implements StorageService{

    private final Logger logger = LoggerFactory.getLogger(StorageServiceImpl.class);

    private Path rootPath;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Value("${file-store.upload.chunkSize}")
    private long CHUNK_SIZE;

    @Value("${file-store.upload.dir}")
    private String finalDirPath;

    @Autowired
    public StorageServiceImpl(@Value("${file-store.upload.dir}") String location) {
        this.rootPath = Paths.get(location);
    }

    /**
     * 这个方法 千万不要调用 谁调用打死谁
     */
    @Override
    public void deleteAll() {
        logger.info("开发初始化清理数据，start");
        FileSystemUtils.deleteRecursively(rootPath.toFile());
        stringRedisTemplate.delete(Constants.FILE_UPLOAD_STATUS);
        stringRedisTemplate.delete(Constants.FILE_MD5_KEY);
        logger.info("开发初始化清理数据，end");
    }

    @Override
    public void init() {
        try {
            Files.createDirectories(rootPath);
        } catch (FileAlreadyExistsException e) {
            logger.error("文件夹已经存在，无需再创建!");
        } catch (IOException e) {
            logger.error("初始化root文件夹失败: {}",e);
        }
    }


    @Override
    public Map<String,Object> uploadFileByMappedByteBuffer(MultipartFileParam param) throws IOException {
        String fileName = param.getName();
        String subPath = File.separator+ param.getMd5();
        String uploadDirPath = finalDirPath +subPath;
        String tempFileName = fileName + "_tmp";
        File tmpDir = new File(uploadDirPath);
        File tmpFile = new File(uploadDirPath, tempFileName);
        if (!tmpDir.exists()) {
            tmpDir.mkdirs();
        }

        RandomAccessFile tempRaf = null;
        FileChannel fileChannel = null;
        MappedByteBuffer mappedByteBuffer = null;
        try {
            tempRaf = new RandomAccessFile(tmpFile, "rw");
            //写入该分片数据
            long offset = CHUNK_SIZE * param.getChunk();
            byte[] fileData = param.getFile().getBytes();
//            fileChannel = tempRaf.getChannel();
            tempRaf.seek(offset);
            tempRaf.write(fileData);
//            ByteBuffer buf = ByteBuffer.wrap(fileData);
//            fileChannel.write(buf,offset,fileData.length);
//            mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
//            mappedByteBuffer.put(fileData);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            logger.error("{}",e);
            e.printStackTrace();
        } finally {
            if (mappedByteBuffer != null) {
                mappedByteBuffer.force();
                FileMD5Util.freedMappedByteBuffer(mappedByteBuffer);
            }
            if (fileChannel != null) {
                try {
                    fileChannel.force(false);
                    fileChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (tempRaf != null) {
                try {
                    tempRaf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        Map<String,Object> isOk = checkAndSetUploadProgress(param, uploadDirPath);
        if ((boolean)isOk.get("complete")) {
            String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
            String realName = UUIDGenerator.getId()+"."+suffix;

            logger.info("upload complete: {}",tmpFile.getAbsolutePath());
            Map<String,Object> _meta = new HashMap<>();
//            DBObject metadata = new BasicDBObject(_meta);
            String contentType = new MimetypesFileTypeMap().getContentType(fileName);
            _meta.put("contentType",contentType);
            _meta.put("savePath",subPath +File.separator+ realName);
            _meta.put("fileSize",tmpFile.length());
            _meta.put("id", isOk.get("resourceId"));
            _meta.put("fileName", fileName);
            renameFile(tmpFile, realName);
//            ObjectId id = gridFsTemplate.store(in,fileName,contentType,metadata);
//            in.close();
//            // 文件写入完毕，删除本地临时文件
//            logger.warn("delete temp file : {}",tmpDir.getAbsolutePath());
//            delFile(tmpDir);
            return _meta;
        }

        return  null;
    }

    /**
     * 检查并修改文件上传进度
     *
     * @param param
     * @param uploadDirPath
     * @return
     * @throws IOException
     */
    private Map<String,Object> checkAndSetUploadProgress(MultipartFileParam param, String uploadDirPath) {
        Map<String,Object> result = new HashMap<>();
        String fileName = param.getName();
        File confFile = new File(uploadDirPath, fileName + ".conf");
        RandomAccessFile accessConfFile = null;
        byte isComplete = Byte.MAX_VALUE;

        try {
            accessConfFile = new RandomAccessFile(confFile, "rw");
            accessConfFile.setLength(param.getChunks());
            accessConfFile.seek(param.getChunk());
            accessConfFile.write(Byte.MAX_VALUE);
            //completeList 检查是否全部完成,如果数组里是否全部都是(全部分片都成功上传)
            byte[] completeList = FileUtils.readFileToByteArray(confFile);
            for (int i = 0; i < completeList.length && isComplete == Byte.MAX_VALUE; i++) {
                //与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUE
                isComplete = (byte) (isComplete & completeList[i]);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (accessConfFile != null) {
                try {
                    accessConfFile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        if (isComplete == Byte.MAX_VALUE) {
            String resourceId = UUIDGenerator.getId();
            result.put("complete",true);
            result.put("resourceId",resourceId);
            stringRedisTemplate.opsForHash().put(Constants.FILE_UPLOAD_STATUS, param.getMd5(), "true");
//            stringRedisTemplate.opsForValue().set(Constants.FILE_MD5_KEY + param.getMd5(), uploadDirPath + File.separator+ fileName);
            stringRedisTemplate.opsForValue().set(Constants.FILE_MD5_KEY + param.getMd5(), resourceId);
            // 删除临时文件
            confFile.delete();
            return result;
        } else {
            result.put("complete",false);
            if (!stringRedisTemplate.opsForHash().hasKey(Constants.FILE_UPLOAD_STATUS, param.getMd5())) {
                stringRedisTemplate.opsForHash().put(Constants.FILE_UPLOAD_STATUS, param.getMd5(), "false");
            }
            if (!stringRedisTemplate.hasKey(Constants.FILE_MD5_KEY + param.getMd5())) {
                stringRedisTemplate.opsForValue().set(Constants.FILE_MD5_KEY + param.getMd5(), uploadDirPath + File.separator + fileName + ".conf");
            }
            return result;
        }
    }

    /**
     * 文件重命名
     *
     * @param toBeRenamed   将要修改名字的文件
     * @param toFileNewName 新的名字
     * @return
     */
    public boolean renameFile(File toBeRenamed, String toFileNewName) {
        //检查要重命名的文件是否存在，是否是文件
        if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
            logger.info("File does not exist: " + toBeRenamed.getName());
            return false;
        }
        String p = toBeRenamed.getParent();
        File newFile = new File(p + File.separatorChar + toFileNewName);
        //修改文件名
        return toBeRenamed.renameTo(newFile);
    }

    /**
     * 删除目录及其下面的子文件
     * @param file
     * @return
     */
    private boolean delFile(File file) {
        logger.warn("delete file : {}",file.getAbsolutePath());
        if (!file.exists()) {
            return false;
        }

        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f: files) {
                delFile(f);
            }
        }

        return file.delete();
    }
}
